#include "precomp.h"
#include "jconcurrentresultstore.h"

/**
 *
 */
namespace JConcurrent {

/**
 * @brief ResultIteratorBase::ResultIteratorBase
 */
ResultIteratorBase::ResultIteratorBase()
 : mapIterator(jmap<int, ResultItem>::const_iterator()), m_vectorIndex(0) { }
ResultIteratorBase::ResultIteratorBase(jmap<int, ResultItem>::const_iterator _mapIterator, int _vectorIndex)
 : mapIterator(_mapIterator), m_vectorIndex(_vectorIndex) { }

/**
 * @brief ResultIteratorBase::vectorIndex
 * @return
 */
int ResultIteratorBase::vectorIndex() const { return m_vectorIndex; }

/**
 * @brief ResultIteratorBase::resultIndex
 * @return
 */
int ResultIteratorBase::resultIndex() const { return mapIterator->first + m_vectorIndex; }

/**
 * @brief ResultIteratorBase::operator ++
 * @return
 */
ResultIteratorBase ResultIteratorBase::operator++()
{
    if (canIncrementVectorIndex()) {
        ++m_vectorIndex;
    } else {
        ++mapIterator;
        m_vectorIndex = 0;
    }
    return *this;
}

/**
 * @brief ResultIteratorBase::batchSize
 * @return
 */
int ResultIteratorBase::batchSize() const
{
    return mapIterator->second.count();
}

/**
 * @brief ResultIteratorBase::batchedAdvance
 */
void ResultIteratorBase::batchedAdvance()
{
    ++mapIterator;
    m_vectorIndex = 0;
}

/**
 * @brief ResultIteratorBase::operator ==
 * @param other
 * @return
 */
bool ResultIteratorBase::operator==(const ResultIteratorBase &other) const
{
    return (mapIterator == other.mapIterator && m_vectorIndex == other.m_vectorIndex);
}

/**
 * @brief ResultIteratorBase::operator !=
 * @param other
 * @return
 */
bool ResultIteratorBase::operator!=(const ResultIteratorBase &other) const
{
    return !operator==(other);
}

/**
 * @brief ResultIteratorBase::isVector
 * @return
 */
bool ResultIteratorBase::isVector() const
{
    return mapIterator->second.isVector();
}

/**
 * @brief ResultIteratorBase::canIncrementVectorIndex
 * @return
 */
bool ResultIteratorBase::canIncrementVectorIndex() const
{
    return (m_vectorIndex + 1 < mapIterator->second.m_count);
}

/**
 * @brief ResultStoreBase::ResultStoreBase
 */
ResultStoreBase::ResultStoreBase() 
    : insertIndex(0), resultCount(0), m_filterMode(false), filteredResults(0) { }

/**
 * @brief ResultStoreBase::setFilterMode
 * @param enable
 */
void ResultStoreBase::setFilterMode(bool enable)
{
    m_filterMode = enable;
}

/**
 * @brief ResultStoreBase::filterMode
 * @return
 */
bool ResultStoreBase::filterMode() const
{
    return m_filterMode;
}

/**
 * @brief ResultStoreBase::syncResultCount
 */
void ResultStoreBase::syncResultCount()
{
    ResultIteratorBase it = resultAt(resultCount);
    while (it != end()) {
        resultCount += it.batchSize();
        it = resultAt(resultCount);
    }
}

/**
 * @brief ResultStoreBase::insertResultItemIfValid
 * @param index
 * @param resultItem
 */
void ResultStoreBase::insertResultItemIfValid(int index, ResultItem &resultItem)
{
    if (resultItem.isValid()) {
        m_results[index] = resultItem;
        syncResultCount();
    } else {
        filteredResults += resultItem.count();
    }
}

/**
 * @brief ResultStoreBase::insertResultItem
 * @param index
 * @param resultItem
 * @return
 */
int ResultStoreBase::insertResultItem(int index, ResultItem &resultItem)
{
    int storeIndex;
    if (m_filterMode && index != -1 && index > insertIndex) {
        pendingResults[index] = resultItem;
        storeIndex = index;
    } else {
        storeIndex = updateInsertIndex(index, resultItem.count());
        insertResultItemIfValid(storeIndex - filteredResults, resultItem);
    }
    syncPendingResults();
    return storeIndex;
}

/**
 * @brief ResultStoreBase::syncPendingResults
 */
void ResultStoreBase::syncPendingResults()
{
    // check if we can insert any of the pending results:
    jmap<int, ResultItem>::iterator it = pendingResults.begin();
    while (it != pendingResults.end()) {
        int index = it->first;
        if (index != resultCount + filteredResults)
            break;

        ResultItem result = it->second;
        insertResultItemIfValid(index - filteredResults, result);
        pendingResults.erase(it);
        it = pendingResults.begin();
    }
}

/**
 * @brief ResultStoreBase::addResult
 * @param index
 * @param result
 * @return
 */
int ResultStoreBase::addResult(int index, const void *result)
{
    ResultItem resultItem(result, 0); // 0 means "not a vector"
    return insertResultItem(index, resultItem);
}

/**
 * @brief ResultStoreBase::addResults
 * @param index
 * @param results
 * @param vectorSize
 * @param totalCount
 * @return
 */
int ResultStoreBase::addResults(int index, const void *results, int vectorSize, int totalCount) 
{
    if (m_filterMode == false || vectorSize == totalCount) {
        ResultItem resultItem(results, vectorSize);
        return insertResultItem(index, resultItem);
    } else {
        if (vectorSize > 0) {
            ResultItem filteredIn(results, vectorSize);
            insertResultItem(index, filteredIn);
        }
        ResultItem filteredAway(0, totalCount - vectorSize);
        return insertResultItem(index + vectorSize, filteredAway);
    }
}

/**
 * @brief ResultStoreBase::begin
 * @return
 */
ResultIteratorBase ResultStoreBase::begin() const
{
    return ResultIteratorBase(m_results.begin());
}

/**
 * @brief ResultStoreBase::end
 * @return
 */
ResultIteratorBase ResultStoreBase::end() const
{
    return ResultIteratorBase(m_results.end());
}

/**
 * @brief ResultStoreBase::hasNextResult
 * @return
 */
bool ResultStoreBase::hasNextResult() const
{
    return begin() != end();
}

/**
 * @brief ResultStoreBase::resultAt
 * @param index
 * @return
 */
ResultIteratorBase ResultStoreBase::resultAt(int index) const
{
    if (m_results.empty()) {
        return ResultIteratorBase(m_results.end());
    }
    jmap<int, ResultItem>::const_iterator it = m_results.lower_bound(index);

    // lowerBound returns either an iterator to the result or an iterator
    // to the nearest greater index. If the latter happens it might be
    // that the result is stored in a vector at the previous index.
    if (it == m_results.end()) {
        --it;
        if (it->second.isVector() == false) {
            return ResultIteratorBase(m_results.end());
        }
    } else {
        if (it->first > index) {
            if (it == m_results.begin())
                return ResultIteratorBase(m_results.end());
            --it;
        }
    }

    const int vectorIndex = index - it->first;
    
    if (vectorIndex >= it->second.count())
        return ResultIteratorBase(m_results.end());
    else if (it->second.isVector() == false && vectorIndex != 0)
        return ResultIteratorBase(m_results.end());
    return ResultIteratorBase(it, vectorIndex);
}

/**
 * @brief ResultStoreBase::contains
 * @param index
 * @return
 */
bool ResultStoreBase::contains(int index) const
{
    return (resultAt(index) != end());
}

/**
 * @brief ResultStoreBase::count
 * @return
 */
int ResultStoreBase::count() const
{
    return resultCount;
}

// returns the insert index, calling this function with
// index equal to -1 returns the next available index.
int ResultStoreBase::updateInsertIndex(int index, int _count)
{
    if (index == -1) {
        index = insertIndex;
        insertIndex += _count;
    } else {
        insertIndex = qMax(index + _count, insertIndex);
    }
    return index;
}

} // namespace JConcurrent
